using System;
using System.Collections.Generic;
using System.Linq;
using FireStarterModeller.BO;
using Habanero.Base;
using Habanero.Base.Exceptions;
using Habanero.BO;
using Habanero.BO.ClassDefinition;
using Microsoft.Practices.Unity;

namespace FireStarterModeller.Logic
{
    public class DMSolutionPostLoadProcessor
    {
        protected List<IDMClass> AllClasses { get; set; }
        private IUnityContainer Container { get; set; }
        public IList<IDMRelationship> CreatedRelationships { get; protected set; }

        public DMSolutionPostLoadProcessor(IUnityContainer container)
        {
            if (container == null) throw new ArgumentNullException("container");
            Container = container;
            CreatedRelationships = new List<IDMRelationship>();
        }

        public void ProcessSolution(IDMSolution solution)
        {
            var origionalExceptionNotifier = GlobalRegistry.UIExceptionNotifier;
            try
            {
                GlobalRegistry.UIExceptionNotifier = new RecordingExceptionNotifier();
                AllClasses = solution.AllClasses.Select(dmClass => (IDMClass) dmClass).ToList();
                SetDisplayNames();
                ProcessRelationships(AllClasses);
                ProcessInheritance();
                SetDefaultUniqueConstraintNames();
                LinkLookupsToClasses();
                ResetAllCardinalities();
                ResetParentDeleteAction();
            }
            finally
            {
                GlobalRegistry.UIExceptionNotifier = origionalExceptionNotifier;
            }
        }

        private void SetDisplayNames()
        {
            foreach (DMClass dmClass in AllClasses)
            {
                try
                {
                    var allProperties = dmClass.Properties;
                    foreach (var property in allProperties)
                    {
                        if (property.DisplayName == null)
                        {
                            property.DisplayName = DMProperty.GetDisplayNameFromPropertyName(property.PropertyName);
                        }
                    }
                }
                catch (Exception ex)
                {
                    //This is a recording Notifier so will record the error and allow processing to continue
                    GlobalRegistry.UIExceptionNotifier.Notify(ex, "", "Error ");
                }
            }
        }

        private void ResetAllCardinalities()
        {
            ResetCardinality(CreatedRelationships);
        }

        private void ProcessInheritance()
        {
            foreach (DMClass dmClass in AllClasses)
            {
                try
                {
                    var inheritanceRelationship = dmClass.InheritanceSuperClassRelationship;
                    if (inheritanceRelationship.IsNull()) continue;
                    ISuperClassDef inheritanceAsSuperClassDef = dmClass.InheritanceSuperClassRelationship;
                    if (inheritanceRelationship.SuperClassID.IsNull())
                    {
                        var foundSuperClass = FindClass(AllClasses, inheritanceAsSuperClassDef.AssemblyName,
                                                        inheritanceAsSuperClassDef.ClassName);
                        inheritanceRelationship.SuperClass = (DMClass)foundSuperClass;
                    }
                    if (inheritanceAsSuperClassDef.ID.IsNotNull()
                        && inheritanceRelationship.IdentityPropertyID.IsNull())
                    {
                        var foundProperty = dmClass.GetDMProperty(inheritanceAsSuperClassDef.ID);
                        inheritanceRelationship.IdentityProperty = (DMProperty) foundProperty;
                    }
                }
                catch (Exception ex)
                {
                    //This is a recording Notifier so will record the error and allow processing to continue
                    GlobalRegistry.UIExceptionNotifier.Notify(ex, "", "Error ");
                }
            }
        }

        private void SetDefaultUniqueConstraintNames()
        {
            foreach (DMClass dmClass in AllClasses)
            {
                try
                {
                    int ucNo = 1;
                    var allUnamedUCs =
                        dmClass.UniqueConstraints.Where(
                            uniqueConstraint => uniqueConstraint.UniqueConstraintName.IsNullOrEmpty());
                    foreach (var uniqueConstraint in allUnamedUCs)
                    {
                        uniqueConstraint.UniqueConstraintName = "UC" + ucNo;
                        ucNo++;
                    }
                }
                catch (Exception ex)
                {
                    //This is a recording Notifier so will record the error and allow processing to continue
                    GlobalRegistry.UIExceptionNotifier.Notify(ex, "", "Error ");
                }
            }
        }

        public void ProcessRelationships(IEnumerable<IDMClass> classes)
        {
            foreach (DMClass ownerClass in classes)
            {
                foreach (var relationship in ownerClass.RelatedRelationships.ToArray())
                {
                    try
                    {
                        LinkRelPropToProperties(relationship, ownerClass);
                        //if (relationship == null || relationship.ReverseRelationship != null) continue;
						if(relationship == null) continue;

                        if (relationship.ReverseRelationship != null)
                        {
                            SetAllReverseRelProps(relationship, relationship.ReverseRelationship);
                            continue;
                        }
                        if (String.IsNullOrEmpty(relationship.RelatedClassName)) continue;

                        IDMClass relatedClass = FindClass(classes, relationship);
                        if (relatedClass == null)
                        {
                            throw new HabaneroApplicationException(
                                string.Format(
                                    "The related dmClass '{0}' '{1}' for relationship '{2}' on Class '{3}' could not be loaded",
                                    relationship.RelatedAssemblyName, relationship.RelatedClassName,
                                    relationship.RelationshipName, relationship.OwnerClassName));
                        }
                        IDMRelationship foundReverseRelationship = FindReverseRelationship(relatedClass, relationship);
                        if (foundReverseRelationship != null)
                        {
                            SetReverseRelationship(relationship, foundReverseRelationship);
                            SetAllReverseRelProps(relationship, foundReverseRelationship);
                        }
                        else
                        {
                            foundReverseRelationship = CreateReverseRelationship(relatedClass, relationship);

                            CreateReverseRelationshipProps(relationship, foundReverseRelationship);
                            LinkRelPropToProperties(foundReverseRelationship, relatedClass);
                        }
                    }
                    catch (Exception ex)
                    {
                        //This is a recording Notifier so will record the error and allow processing to continue
                        GlobalRegistry.UIExceptionNotifier.Notify(ex, "", "Error ");
                    }
                }
            }
        }

        public void ResetParentDeleteAction()
        {
            foreach (DMClass ownerClass in AllClasses)
            {
                foreach (var relationship in ownerClass.RelatedRelationships.ToArray())
                {
                    ResetParentDeleteAction(relationship);
                }
            }
        }

        public void ResetParentDeleteAction(DMRelationship relationship)
        {
            if (relationship.IsManyToOne && !relationship.IsOneDeleteActionDoNothing)
            {
                relationship.DeleteAction = DeleteParentAction.DoNothing;
            }
        }


        private void SetAllReverseRelProps(IDMRelationship relationship, IDMRelationship foundReverseRelationship)
        {
            //Convert from interfaces into concrete classes.
            var relProps = relationship.RelationshipProperties.Select(property => (IDMRelationshipProperty) property);
            var reverseRepProps =
                foundReverseRelationship.RelationshipProperties.Select(property => (IDMRelationshipProperty) property);

            SetAllReverseRelProps(relProps, reverseRepProps);
        }

        private void LinkLookupsToClasses()
        {
            foreach (DMClass @class in AllClasses)
            {
                LinkLookupsForClass(@class);
            }
        }

        private void LinkLookupsForClass(IDMClass dmClass)
        {
            if (dmClass == null) return;
            var boLookupLists = from property in dmClass.Properties
                                where (property.LookupList is DMLookupListBusinessObject)
                                select (DMLookupListBusinessObject) property.LookupList;

            foreach (DMLookupListBusinessObject boLookupList in boLookupLists)
            {
                boLookupList.DMClass =
                    (DMClass) FindClass(AllClasses, boLookupList.AssemblyName, boLookupList.ClassName);
            }
        }

        public IDMRelationship CreateReverseRelationship(IDMClass relatedClass, IDMRelationship relationship)
        {
            if (relatedClass.IsNull() || relationship.IsNull()) return null;
            var reverseRelationship = Container.Resolve<IDMRelationship>();
            SetReverseRelationship(reverseRelationship, relationship);
            reverseRelationship.OwnerClass = relatedClass;
            if (relationship.IsSingle)
            {
                reverseRelationship.Cardinality = Cardinality.Multiple;
            }
            else
            {
                reverseRelationship.Cardinality = Cardinality.Single;
                reverseRelationship.DeleteAction = DeleteParentAction.DoNothing;
            }
            reverseRelationship.RelationshipName =
                new RelationshipNameGenerator().CreateRelationshipName(reverseRelationship);
            reverseRelationship.GenerateCode = false;
            CreatedRelationships.Add(reverseRelationship);
            return reverseRelationship;
        }

        public static void SetReverseRelationship(IDMRelationship relationship, IDMRelationship reverseRelationship)
        {
            relationship.ReverseRelationship = null;
            reverseRelationship.ReverseRelationship = null;
            relationship.ReverseRelationship = reverseRelationship;
            reverseRelationship.ReverseRelationship = relationship;
        }

        public static BusinessObjectCollection<DMRelationshipProperty> CreateReverseRelationshipProps
            (IDMRelationship relationship, IDMRelationship reverseRelationship)
        {
            var relationshipProps = reverseRelationship.RelationshipProperties;
            foreach (var relationshipProperty in relationship.RelationshipProperties.ToArray())
            {
                var reverseRelationshipProp = new DMRelationshipProperty(relationshipProperty)
                                                  {
                                                      OwnerPropertyName = relationshipProperty.RelatedPropertyName,
                                                      RelatedPropertyName = relationshipProperty.OwnerPropertyName
                                                  };
                relationshipProps.Add(reverseRelationshipProp);
            }
            return relationshipProps;
        }

        public void LinkRelPropToProperties(IDMRelationship relationship, IDMClass ownerClass)
        {
            if (ownerClass == null) return;
            if (relationship == null) return;
            foreach (var relationshipProperty in relationship.RelationshipProperties.ToArray())
            {
                if (relationshipProperty.PropertyID.IsNotNull()) continue;
                relationshipProperty.OwnerProperty = ownerClass.GetDMProperty(relationshipProperty.OwnerPropertyName);
            }
        }

        public IDMRelationship FindReverseRelationship(IDMClass relatedClass, IDMRelationship relationship)
        {
            if (relatedClass.IsNull() || relationship.IsNull() || relatedClass.RelationshipsDM.IsNull()) return null;

            var relationships = relatedClass.RelationshipsDM;

            var ownerClassName = relationship.OwnerClassName == null ? "" : relationship.OwnerClassName.ToUpper();
            var ownerClassAssemblyName = relationship.OwnerClassAssemblyName == null
                                             ? ""
                                             : relationship.OwnerClassAssemblyName.ToUpper();
            var potentialReverseRelationships = relationships.Where(revRel
                    => revRel.RelatedClassName.IsNotNull() 
                    && revRel.RelatedAssemblyName.IsNotNull()
                    && revRel.RelatedClassName.ToUpper() == ownerClassName
                    && revRel.RelatedAssemblyName.ToUpper() == ownerClassAssemblyName
                    && revRel.ReverseRelationship.IsNull());

            var foundReverseRelationship
                = FindReverseRelationshipThatMatchExactly(relationship, potentialReverseRelationships);


            if (foundReverseRelationship.IsNotNull()) return foundReverseRelationship;

            foundReverseRelationship
                = FindReverseRelationshipThatMatchesEitherReverse(relationship, potentialReverseRelationships);

            if (foundReverseRelationship.IsNotNull()) return foundReverseRelationship;

            if (relationship.ReverseRelationshipName.IsNotNullOrEmpty()) return null;
            return FindReverseRelFromRelProps(relationship, potentialReverseRelationships);
                // : foundReverseRelationship;
        }

        private DMRelationship FindReverseRelationshipThatMatchesEitherReverse(IDMRelationship relationship,
                                                                               IEnumerable<DMRelationship>
                                                                                   potentialReverseRelationships)
        {
            return potentialReverseRelationships.FirstOrDefault(
                revRel => (revRel.ReverseRelationshipName.IsNullOrEmpty()
                           && revRel.RelationshipName == relationship.ReverseRelationshipName)
                          || (relationship.ReverseRelationshipName.IsNullOrEmpty()
                              && revRel.ReverseRelationshipName == relationship.RelationshipName));
        }

        private DMRelationship FindReverseRelationshipThatMatchExactly(IDMRelationship relationship,
                                                                       IEnumerable<DMRelationship>
                                                                           potentialReverseRelationships)
        {
            return potentialReverseRelationships.FirstOrDefault(
                revRel => revRel.RelationshipName == relationship.ReverseRelationshipName
                          && revRel.ReverseRelationshipName == relationship.RelationshipName);
        }

        private IDMRelationship FindReverseRelFromRelProps(IDMRelationship relationship,
                                                           IEnumerable<DMRelationship> relationships)
        {
            foreach (var potentialReverseRelationship in relationships)
            {
                if (potentialReverseRelationship.ReverseRelationshipName.IsNotNullOrEmpty()) continue;
                bool found = potentialReverseRelationship.RelationshipProperties.Count > 0;
                var anyNonMatchingRevRelProps = (from potentialRevRelProps in
                                                     potentialReverseRelationship.RelationshipProperties
                                                 let ownerPropertyName = potentialRevRelProps.OwnerPropertyName
                                                 let relatedPropertyName = potentialRevRelProps.RelatedPropertyName
                                                 select relationship.RelationshipProperties.FirstOrDefault(property
                                                       => property.RelatedPropertyName.IsNotNullOrEmpty()
                                                       && ownerPropertyName.IsNotNullOrEmpty()
                                                       && relatedPropertyName.IsNotNullOrEmpty()
                                                       && property.OwnerPropertyName.IsNotNullOrEmpty()
                                                       && property.RelatedPropertyName == ownerPropertyName
                                                       && property.OwnerPropertyName == relatedPropertyName))
                                             .Any(foundRevRelProp => foundRevRelProp.IsNull());

                if (anyNonMatchingRevRelProps)
                {
                    found = false;
                }
                if (found) return potentialReverseRelationship;
            }
            return null;
        }

        public IDMClass FindClass(IEnumerable<IDMClass> col, IDMRelationship relationship)
        {
            if (col == null || relationship == null) return null;

            var relatedAssemblyName = relationship.RelatedAssemblyName;
            var relatedClassName = relationship.RelatedClassName;
            return FindClass(col, relatedAssemblyName, relatedClassName);
        }

        private static IDMClass FindClass(IEnumerable<IDMClass> col, string relatedAssemblyName, string relatedClassName)
        {
            var upperRelatedAssemblyname = relatedAssemblyName == null ? "" : relatedAssemblyName.ToUpper();
            var upperClassName = relatedClassName == null ? "" : relatedClassName.ToUpper();
            return col.FirstOrDefault(dmclass
                                      => dmclass.AssemblyName.ToUpper() == upperRelatedAssemblyname
                                         && dmclass.ClassNameBO.ToUpper() == upperClassName);
        }

        public void SetReverseRelationshipProperty(DMRelationshipProperty relProp, DMRelationshipProperty reverseRelProp)
        {
            relProp.ReverseRelationshipProperty = null;
            reverseRelProp.ReverseRelationshipProperty = null;
            relProp.ReverseRelationshipProperty = reverseRelProp;
            reverseRelProp.ReverseRelationshipProperty = relProp;
        }

        public IDMRelationshipProperty FindReverseRelationshipProperty(IEnumerable<IDMRelationshipProperty> relProps,
                                                                       IDMRelationshipProperty relationshipProperty)
        {
            if (relProps.IsNull() || relationshipProperty.IsNull()) return null;
            return relProps.FirstOrDefault(property
                                           => property.OwnerPropertyName == relationshipProperty.RelatedPropertyName
                                              && property.RelatedPropertyName == relationshipProperty.OwnerPropertyName);
        }

        public void SetAllReverseRelProps(IEnumerable<IDMRelationshipProperty> ownerRelProps,
                                          IEnumerable<IDMRelationshipProperty> reverseRelProps)
        {
            if (ownerRelProps.IsNull() || reverseRelProps.IsNull()) return;
            foreach (var relationshipProperty in ownerRelProps)
            {
                if(relationshipProperty.ReverseRelationshipProperty.IsNotNull()) continue;
                var reverseRelProp = FindReverseRelationshipProperty(reverseRelProps, relationshipProperty);
                if (reverseRelProp == null) continue;
                if (reverseRelProp.ReverseRelationshipProperty.IsNotNull()) continue;
                relationshipProperty.ReverseRelationshipProperty = reverseRelProp;
                reverseRelProp.ReverseRelationshipProperty = relationshipProperty;
            }
        }

        public void ResetCardinality(IEnumerable<IDMRelationship> relationships)
        {
            foreach (var relationship in relationships)
            {
                ((DMRelationship) relationship).ResetCardinality();
            }
        }
    }


    public static class TypeExtensions
    {
        public static bool IsNotNullOrEmpty(this string item)
        {
            return !string.IsNullOrEmpty(item);
        }

        public static bool IsNullOrEmpty(this string item)
        {
            return string.IsNullOrEmpty(item);
        }

        public static bool IsNull(this object item)
        {
            return item == null;
        }

        public static bool IsNotNull(this object item)
        {
            return item != null;
        }
    }
}